﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Linq;
using System.Collections.ObjectModel;

namespace LegtionMVVM
{
    /// <summary>
    /// 创建代理类。
    /// 获取原类的所有字段及属性。
    /// 对比原有字段，命名不重复的字段，用于属性本身的。
    /// 重载 public 的属性及  get_Method set_Method
    /// </summary>
    public class PropertyAop
    {
        static Dictionary<string, object> createFuncList = new Dictionary<string, object>();
        static Dictionary<string, Type> proxyTypeList = new Dictionary<string, Type>();

        private static AssemblyName assemblyName = new AssemblyName("LegtionMVVM_AOPAssembly");
        private static AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
            assemblyName, AssemblyBuilderAccess.RunAndSave);
        private static ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("LegtionMVVM_AOPModule", typeAfterName + ".dll");
        const string typeAfterName = "_LegtionType";

        public static T Create<T>()
        {
            return Create<T>(typeof(T));
        }

        public static T Create<T>(Type type)
        {
            object obj = null;
            if (!createFuncList.TryGetValue(type.FullName, out obj))
            {
                Type proxyType = null;
                lock (proxyTypeList)
                {
                    string key = type.FullName + typeAfterName;
                    if (!proxyTypeList.TryGetValue(key, out proxyType))
                    {
                        proxyType = CreateProxyType(type);
                        assemblyBuilder.Save(typeAfterName + ".dll");
                        // proxyTypeList.Add(key, proxyType);
                    }
                }
                lock (createFuncList)
                {
                    if (!createFuncList.TryGetValue(type.FullName, out obj))
                    {
                        obj = CreateFunc<T>(proxyType);
                        createFuncList.Add(type.FullName, obj);
                    }
                }
            }

            Func<T> func = obj as Func<T>;
            if (func == null)
                throw new Exception("所需要创建的类型不符合需求。请查看是否有无参构造函数。");

            return func();
        }
        private static Func<T> CreateFunc<T>(Type type)
        {
            DynamicMethod method = new DynamicMethod("", typeof(T), null);
            ILGenerator il = method.GetILGenerator();
            ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);
            if (constructor == null) return null;
            il.Emit(OpCodes.Newobj, constructor);
            il.Emit(OpCodes.Ret);

            return method.CreateDelegate(typeof(Func<T>)) as Func<T>;
        }
        private static Type CreateProxyType(Type type, Type baseVMType = null)
        {
            string key = type.FullName + typeAfterName;
            Type resultType = null;
            if (proxyTypeList.TryGetValue(key, out resultType))
            {
                return resultType;
            }
            TypeBuilder typeBuilder = moduleBuilder.DefineType(type.FullName + typeAfterName, TypeAttributes.Class | TypeAttributes.Public, type, Type.EmptyTypes);

            FieldInfo[] fieldInfos = type.GetFields();
            List<string> listFieldNames = new List<string>();
            foreach (FieldInfo field in fieldInfos)
                listFieldNames.Add(field.Name.ToLower());

            PropertyInfo[] propertyInfos = type.GetProperties();
            Dictionary<string, string> propertyToPrivateFieldNames = new Dictionary<string, string>();
            Random rand = new Random(DateTime.Now.Millisecond);

            if (type.BaseType == typeof(NotifyPropertyChanged))
                baseVMType = type.BaseType;
            foreach (PropertyInfo property in propertyInfos)
            {
                string newField = "_" + property.Name[0].ToString().ToLower() + property.Name.Substring(1);
                if (listFieldNames.Contains(newField.ToLower()))
                    newField += rand.Next(100, 10000);
                propertyToPrivateFieldNames.Add(property.Name, newField);

                OverrideProperty(typeBuilder, property, newField, baseVMType);
            }
            resultType = typeBuilder.CreateType();
            proxyTypeList.Add(key, resultType);

            Source.SetTypeSource(type);
            Source.SetTypeSource(resultType);

            return resultType;
        }  
        private static void OverrideProperty(TypeBuilder typeBuilder, PropertyInfo propertyInfo, string newPrivateField,Type baseVMType=null)
        {
            Type proxyType = propertyInfo.PropertyType;// a proxyType
            bool isCreateProxyType = false;
            bool isGenericType = propertyInfo.PropertyType.IsGenericType;
            if (propertyInfo.PropertyType.IsClass && !propertyInfo.PropertyType.IsValueType
                && propertyInfo.PropertyType.Name.ToLower() != "string" && propertyInfo.PropertyType.Name.ToLower() != "object")
            {
                if (propertyInfo.PropertyType.IsGenericType)
                {
                    Type[] genericTypes = propertyInfo.PropertyType.GetGenericArguments();
                    List<Type> listProxyGenericTypes = new List<Type>();
                    foreach (Type genericType in genericTypes)
                    {
                        Type tempType = genericType;
                        if (tempType.IsClass && !tempType.IsValueType
                && tempType.Name.ToLower() != "string" && tempType.Name.ToLower() != "object")
                            tempType = CreateProxyType(genericType);
                        listProxyGenericTypes.Add(tempType);
                    }
                    proxyType = listProxyGenericTypes[0];
                    //Type genericTypeDefinition = proxyType.GetGenericTypeDefinition();
                    //proxyType = genericTypeDefinition.MakeGenericType(listProxyGenericTypes.ToArray());
                }
                else
                {
                    proxyType = CreateProxyType(propertyInfo.PropertyType,baseVMType);
                    isCreateProxyType = true;
                }
            }
            MethodInfo getMethod = propertyInfo.GetGetMethod();
            MethodInfo setMethod = propertyInfo.GetSetMethod();
            if ((getMethod == null && setMethod == null)
                || (getMethod != null && !getMethod.IsVirtual)
                || (setMethod != null && !setMethod.IsVirtual))
                return;

            FieldBuilder fieldBuilder = typeBuilder.DefineField(newPrivateField, proxyType, FieldAttributes.Private);

            PropertyBuilder propertybuilder = typeBuilder.DefineProperty(propertyInfo.Name, propertyInfo.Attributes, propertyInfo.PropertyType, null);
            if (getMethod != null)
            {
                MethodBuilder getPropertybuilder = typeBuilder.DefineMethod("get_" + propertyInfo.Name, MethodAttributes.Public | MethodAttributes.Family
                | MethodAttributes.HideBySig | MethodAttributes.Virtual, propertyInfo.PropertyType, Type.EmptyTypes);
                ILGenerator getIL = getPropertybuilder.GetILGenerator();

                getIL.Emit(OpCodes.Ldarg_0);
                getIL.Emit(OpCodes.Ldfld, fieldBuilder);
                #region 使用非值属性
                if (isGenericType)
                {
                    string strGetInstanceMethodName = "GetGenericInstanceFromDerivedClass";
                    MethodInfo getInstanceMethod = typeof(PropertyAop).GetMethod(strGetInstanceMethodName);
                    //首先获得目标方法的MethodInfo
                    List<Type> lstTypeArguments = new List<Type> { proxyType,propertyInfo.PropertyType.GetGenericArguments()[0], propertyInfo.PropertyType };

                    MethodInfo makeGenericMethodInfo = getInstanceMethod.MakeGenericMethod(lstTypeArguments.ToArray());
                    //声明本地变量
                    ParameterInfo[] pis = makeGenericMethodInfo.GetParameters();
                    for (int i = 0; i < pis.Length; ++i)
                    {
                        getIL.DeclareLocal(pis[i].ParameterType);
                    }
                    //执行目标方法
                    getIL.Emit(OpCodes.Call, makeGenericMethodInfo);
                }
                #endregion
                getIL.Emit(OpCodes.Ret);

                propertybuilder.SetGetMethod(getPropertybuilder);
            }
            if (setMethod != null)
            {
                MethodBuilder setPropertyBuilder = typeBuilder.DefineMethod("set_" + propertyInfo.Name, MethodAttributes.Public | MethodAttributes.Family
                | MethodAttributes.HideBySig | MethodAttributes.Virtual, null, new Type[] { propertyInfo.PropertyType });
                ILGenerator setIL = setPropertyBuilder.GetILGenerator();

                setIL.Emit(OpCodes.Ldarg_0);
                //setIL.Emit(OpCodes.Ldfld, typeBuilder);
                setIL.Emit(OpCodes.Ldarg_1);               
                
                #region 使用非值类型属性
                if (isCreateProxyType || isGenericType)
                {
                    string strCreateInstanceMethodName = isGenericType ? "CreateInstanceFromGenericType" : "CreateInstanceFromBaseClass";
                    MethodInfo createInstanceMethod = typeof(PropertyAop).GetMethod(strCreateInstanceMethodName);
                    //首先获得目标方法的MethodInfo
                    List<Type> lstTypeArguments = new List<Type>();
                    if (isGenericType)//泛型
                    {
                        setIL.Emit(OpCodes.Ldarg_0);

                        lstTypeArguments.Add(proxyType);
                        lstTypeArguments.Add(propertyInfo.PropertyType.GetGenericArguments()[0]);
                        lstTypeArguments.Add(propertyInfo.PropertyType);
                        lstTypeArguments.Add(propertyInfo.DeclaringType);
                    }
                    else
                    {
                        //setIL.Emit(OpCodes.Ldarg_0);
                        lstTypeArguments.Add(proxyType);
                        lstTypeArguments.Add(propertyInfo.PropertyType);                        
                    }
                    MethodInfo makeGenericMethodInfo = createInstanceMethod.MakeGenericMethod(lstTypeArguments.ToArray());
                    //声明本地变量
                    ParameterInfo[] pis = makeGenericMethodInfo.GetParameters();
                    for (int i = 0; i < pis.Length; ++i)
                    {
                        setIL.DeclareLocal(pis[i].ParameterType);
                    }
                    //执行目标方法
                    setIL.Emit(OpCodes.Call, makeGenericMethodInfo);
                }
                #endregion

                //setAIL.Emit(OpCodes.Stfld, fieldBuilder);
                //setAIL.Emit(OpCodes.Ldstr, "HelloWorld!");
                //setAIL.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));
                //setIL.Emit(OpCodes.Call, callMethod);
                setIL.Emit(OpCodes.Stfld, fieldBuilder);

                if (baseVMType != null)
                {
                    setIL.Emit(OpCodes.Ldarg_0);
                    setIL.Emit(OpCodes.Ldstr, propertyInfo.DeclaringType.Name + "." + propertyInfo.Name);
                    setIL.Emit(OpCodes.Call, baseVMType.GetMethod("RaisePropertyChanged", new Type[] { typeof(string) }));
                }
                

                //setIL.Emit(OpCodes.Ldstr, propertyInfo.DeclaringType.Name + "." + propertyInfo.Name);
                //setIL.Emit(OpCodes.Call, typeof(PropertyAop).GetMethod("NotifyPropertyChanged", new Type[] { typeof(string) }));
                setIL.Emit(OpCodes.Ret);

                propertybuilder.SetSetMethod(setPropertyBuilder);
            }

        }
        public static void NotifyPropertyChanged(string propertyFullName)
        {
        }
        public static TP CreateInstanceFromBaseClass<TP, TB>(TB instance) where TP : TB
        {
            if (instance == null)
                return default(TP);
            Type proxyType = typeof(TP);
            PropertyInfo[] proxyPInfos = proxyType.GetProperties();
            Type baseType = typeof(TB);
            PropertyInfo[] propertyInfos = baseType.GetProperties();
            TP proxyInstance = (TP)Activator.CreateInstance(proxyType);

            foreach (PropertyInfo proxyProperty in proxyPInfos)
            {
                PropertyInfo baseProperty = propertyInfos.AsEnumerable().FirstOrDefault(item => item.Name == proxyProperty.Name);
                if (baseProperty != null)
                {
                    MethodInfo baseGetMethod = baseProperty.GetGetMethod();
                    MethodInfo proxySetMethod = proxyProperty.GetSetMethod();
                    if (baseGetMethod != null && proxySetMethod != null)
                    {
                        object baseObj = baseGetMethod.Invoke(instance, null);
                        proxySetMethod.Invoke(proxyInstance, new object[] { baseObj });
                    }
                }
            }
            return proxyInstance;
        }
        public static TGB CreateInstanceFromGenericType<TP, TB, TGB, TD>(TGB instance, TD td) where TP : TB
        {
            Type typeTGB = typeof(TGB);
            if (typeTGB.Name.Contains("ObservableCollection"))
            {
                Type typeViewModel = td.GetType();
                PropertyInfo[] properties = typeViewModel.GetProperties();
                PropertyInfo currPropertyInfo=null;
                foreach (PropertyInfo p in properties)
                {
                    if (p.PropertyType == typeTGB)
                    {
                        currPropertyInfo = p;
                    }
                }
                if (currPropertyInfo != null)
                {
                    EventInfo evClick = typeTGB.GetEvent("CollectionChanged");
                    Type tDelegate = evClick.EventHandlerType;
  
                    MethodInfo miHandler = typeViewModel.GetMethod("OnCollectionChanged",BindingFlags.NonPublic |BindingFlags.Instance);

                    Delegate d = Delegate.CreateDelegate(tDelegate, td, miHandler);

                    MethodInfo addHandler = evClick.GetAddMethod();
                    Object[] addHandlerArgs = { d };
                    addHandler.Invoke(instance, addHandlerArgs);
                }
            }

            if (typeTGB.Name.Contains("ObservableCollection"))
            {
            }                
            return instance;
        }
        public static TGB GetGenericInstanceFromDerivedClass<TP, TB, TGB>(TGB instance) where TP : TB
        {
            return instance;
        }
 
        public static void ObservableCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
            }
            System.Windows.Forms.MessageBox.Show("Use CollectionChanged EventHandler");
        }
        
    }
}
